var Point = require('./math/point');
var Matrix = require('./math/matrix');


module.exports = class ImageUtil {
    constructor() { }
    // Note：图片插值算法：opencv 里，
    // INTER_NEAREST      -最近邻插值 (效果很差)
    // INTER_LINEAR       -双线性插值 (默认使用)
    // INTER_AREA         -区域插值。
    // INTER_CUBIC        -三次样条插值，也叫立方插值（超过4*4像素邻域内的双三次插值）
    // INTER_LANCZOS4     -Lanczos插值（超过8*8像素邻域的Lanczos插值）、
    // 缩小图片 使用 INTER_AREA 算法，放大图片使用 INTER_LINEAR/INTER_CUBIC 算法

    /**
     * 数据插值
     * @param w 目标矩阵宽度
     * @param h 目标矩阵高度
     * @param data 源数据矩阵（二维数组）
     * @param type 插值方式，1：双线性插值，2：双三次插值法
     */
    scaleData = function (w, h, data, type = 2) {
        let sw = data.length;
        let sh = data[0].length;

        let resData = new Array(w);
        for (let i = 0; i < w; i++) {
            let line = new Array(h);
            for (let j = 0; j < h; j++) {
                let v;
                if (type === 2) {
                    // 双三次插值法
                    v = this.cubicInterpolation(w, h, i, j, data, sw, sh);
                } else if (type === 1) {
                    // 双线性插值
                    v = this.interpolation(w, h, i, j, data, sw, sh);
                } else {
                    throw new Error('scale data, type not supported(type must be 1 or 2)');
                }
                line[j] = v;
            }
            resData[i] = line;
        }
        return resData;
    }

    /**
     * 双线性插值
     * @param sw 目标矩阵的宽度
     * @param sh 目标矩阵的高度
     * @param x_ 目标矩阵中的x坐标
     * @param y_ 目标矩阵中的y坐标
     * @param data 源数据矩阵（二维数组）
     * @param w 源矩阵的宽度
     * @param h 源矩阵的高度
     */
    interpolation = function (sw, sh, x_, y_, data, w, h) {

        let x = (x_ + 0.5) * w / sw - 0.5;
        let y = (y_ + 0.5) * h / sh - 0.5;

        let x1 = Math.floor(x);
        let x2 = Math.floor(x + 0.5);
        let y1 = Math.floor(y);
        let y2 = Math.floor(y + 0.5);

        x1 = x1 < 0 ? 0 : x1;
        y1 = y1 < 0 ? 0 : y1;


        x1 = x1 < w - 1 ? x1 : w - 1;
        y1 = y1 < h - 1 ? y1 : h - 1;

        x2 = x2 < w - 1 ? x2 : w - 1;
        y2 = y2 < h - 1 ? y2 : h - 1;

        // 取出原矩阵中对应四个点的值
        let f11 = data[x1][y1];
        let f21 = data[x1][y2];
        let f12 = data[x2][y1];
        let f22 = data[x2][y2];
        // 计算该点的值
        let xm = x - x1;
        let ym = y - y1;
        let r1 = (1 - xm) * f11 + xm * f21;
        let r2 = (1 - xm) * f12 + xm * f22;
        let value = (1 - ym) * r1 + ym * r2;

        value = Math.round(value);
        if (value < 0) {
            value = 0;
        }
        if (value > 255) {
            value = 255;
        }
        return value;
    }

    /**
     * 双三次插值法
     * @param sw 目标矩阵的宽度
     * @param sh 目标矩阵的高度
     * @param x_ 目标矩阵中的x坐标
     * @param y_ 目标矩阵中的y坐标
     * @param data 源数据矩阵（二维数组）
     * @param w 源矩阵的宽度
     * @param h 源矩阵的高度
     */
    cubicInterpolation = function (sw, sh, x_, y_, data, w, h) {
        // 计算缩放后坐标对应源数据上的坐标
        let x = x_ * w / sw;
        let y = y_ * h / sh;


        // 计算x和y方向的最近的4*4的坐标和权重
        let wcx = this.getCubicWeight(x);
        let wcy = this.getCubicWeight(y);

        // 权重
        let wx = wcx.weight;
        let wy = wcy.weight;

        // 坐标
        let xs = wcx.coordinate;
        let ys = wcy.coordinate;

        let val = 0;
        // 遍历周围4*4的点，根据权重相加
        for (let j = 0; j < 4; j++) {
            let py = ys[j];
            py = py < 0 ? 0 : py;
            py = py > h - 1 ? h - 1 : py;
            for (let i = 0; i < 4; i++) {
                let px = xs[i];
                px = px < 0 ? 0 : px;
                px = px > w - 1 ? w - 1 : px;
                // 该点的值
                let dv = data[px][py];
                // 该点的权重
                let w_x = wx[i];
                let w_y = wy[j];
                // 根据加权加起来
                val += (dv * w_x * w_y);
            }
        }
        val = Math.round(val);
        if (val < 0) {
            val = 0;
        }
        if (val > 255) {
            val = 255;
        }
        return val;
    }

    /**
     * 双三次插值法中，基于BiCubic基函数，计算源坐标v，最近的4*4的坐标和坐标对应的权重
     * @param v 目标矩阵中坐标对应在源矩阵中坐标值
     */
    getCubicWeight = function (v) {
        let a = -0.5;

        // 取整
        let nv = Math.floor(v);

        // 坐标差值集合
        let xList = new Array(4);
        // 坐标集合
        let xs = new Array(4);

        // 最近的4个坐标差值
        xList[0] = nv - v - 1;
        xList[1] = nv - v
        xList[2] = nv - v + 1;
        xList[3] = nv - v + 2;
        // 
        xs[0] = nv - 1;
        xs[1] = nv;
        xs[2] = nv + 1;
        xs[3] = nv + 2;

        // 计算权重
        let ws = new Array(4);
        for (let i = 0; i < 4; i++) {
            let val = Math.abs(xList[i]);
            let w = 0;
            // 基于BiCubic基函数的双三次插值
            if (val <= 1) {
                w = (a + 2) * val * val * val - (a + 3) * val * val + 1;
            } else if (val < 2) {
                w = a * val * val * val - 5 * a * val * val + 8 * a * val - 4 * a;
            }
            ws[i] = w;
        }

        return {
            weight: ws,
            coordinate: xs
        };
    }

    /**
     * 
     * @param {*} data 
     * @param {*} w 
     * @param {*} h 
     * @param {*} flipCode 翻转码，
     *          0 : 表示沿 X 轴翻转
     *        > 0 : 如 1，表示沿 Y 轴翻转，
     *        < 0 : 如 -1，表示既沿 X 轴翻转，又沿 Y 轴翻转，等价于旋转180°
    * @returns 无返回，直接在原矩阵上修改
     */
    flip = function (data, w, h, flipCode) {
        let temp = 0;
        if (flipCode === 0) {
            for (let i = 0; i < w; i++) {
                for (let j = 0; j < h / 2; j++) {
                    temp = data[i][j];
                    data[i][j] = data[i][h - 1 - j];
                    data[i][h - 1 - j] = temp;
                }
            }
        } else if (flipCode > 0) { // 
            for (let i = 0; i < w / 2; i++) {
                for (let j = 0; j < h; j++) {
                    temp = data[i][j];
                    data[i][j] = data[w - 1 - i][j];
                    data[w - 1 - i][j] = temp;
                }
            }
        } else {
            for (let i = 0; i < w / 2; i++) {
                for (let j = 0; j < h; j++) {
                    temp = data[i][j];
                    data[i][j] = data[w - 1 - i][h - 1 - j];
                    data[w - 1 - i][h - 1 - j] = temp;
                }
            }
        }

    }

    /**
     * 转置，相当于沿对角线翻转
     * @param {*} data 
     * @param {*} w 
     * @param {*} h 
     */
    transpose = function (data, w, h) {
        let resData = new Array(h);
        for (let i = 0; i < h; i++) {
            resData[i] = new Array(w); // 初始化每一行的数组
            for (let j = 0; j < w; j++) {
                resData[i][j] = data[j][i]; // 进行转置操作
            }
        }
        return resData; // 返回转置后的矩阵
    }

    /**
     * 
     * @param {*} data 源矩阵
     * @param {*} w 源矩阵宽度
     * @param {*} h 源矩阵高度
     * @param {*} steps 旋转角度
     */
    rotate90degrees = function (data, w, h, steps) {
        // let steps = Math.round(deg / 90) % 4; // 四舍五入至最接近的90度倍数
        // steps += steps < 0 ? 4 : 0;

        let resData;

        switch (steps) {
            case 0: // 没有变化
                resData = data;
                break;
            case 1: // 90度 或  -270度
                resData = this.transpose(data, w, h);
                this.flip(resData, h, w, 1);
                break;
            case 2: // 180度
                this.flip(data, w, h, -1);
                resData = data;
                break;
            case 3:
                resData = this.transpose(data, w, h)
                this.flip(resData, h, w, 0);
                break;
        }
        return resData;
    }

    rotateBilinear = function (
        srcData,
        dstData,
        incrementX,
        incrementY,
        x0,
        y0,
        cos,
        sin,
    ) {
        let dstDataWidth = dstData.length;
        let dstDataHeight = dstData[0].length;

        let srcDataWidth = srcData.length;
        let srcDataHeight = srcData[0].length;

        for (let j = 0; j < dstDataHeight; j++) {
            for (let i = 0; i < dstDataWidth; i++) {
                let x = (i - x0) * cos - (j - y0) * sin + x0 + incrementX;
                let y = (j - y0) * cos + (i - x0) * sin + y0 + incrementY;
                let x1 = x | 0;
                let y1 = y | 0;
                let xDiff = x - x1;
                let yDiff = y - y1;

                if (x < 0 || x >= srcDataWidth - 1 || y < 0 || y >= srcDataHeight - 1) {
                    dstData[i][j] = 255;
                } else {
                    let A = srcData[x1][y1];
                    let B = srcData[x1 + 1][y1];
                    let C = srcData[x1][y1 + 1];
                    let D = srcData[x1 + 1][y1 + 1];
                    let result =
                        (A +
                            xDiff * (B - A) +
                            yDiff * (C - A) +
                            xDiff * yDiff * (A - B - C + D)) |
                        0;
                    dstData[i][j] = result;
                }
            }
        }

        return dstData;
    }

    /**
     * 
     * @param {*} data 源矩阵
     * @param {*} smatrix 变换矩阵 
     * @param {*} scale 缩放比例，默认1
     */
    transform = function (data, smatrix, sx, sy) {

        let srcw = data.length;
        let srch = data[0].length;

        // 先不加平移计算和缩放
        // TODO：这里的旋转方向也很诡异
        let matrix = new Matrix(smatrix[0] / sx, -smatrix[1] / sx, -smatrix[2] / sy, smatrix[3] / sy, 0, 0);

        // 计算四个顶点的坐标
        // let x1y0 = matrix.transformPoint(new Point(0, srcw));
        // let y0y1 = matrix.transformPoint(new Point(srch, 0));
        // let x1y1 = matrix.transformPoint(new Point(srch, srcw));

        let x1y0 = matrix.transformPoint(new Point(srcw, 0));
        let y0y1 = matrix.transformPoint(new Point(0, srch));
        let x1y1 = matrix.transformPoint(new Point(srcw, srch));

        // 计算目标矩阵的尺寸
        let minX = Math.min(x1y0.x, y0y1.x, x1y1.x, 0);
        let maxX = Math.max(x1y0.x, y0y1.x, x1y1.x, 0);
        let minY = Math.min(x1y0.y, y0y1.y, x1y1.y, 0);
        let maxY = Math.max(x1y0.y, y0y1.y, x1y1.y, 0);

        let dstw = Math.ceil(maxX - minX);
        let dsth = Math.ceil(maxY - minY);

        if (dstw % 2 !== 0) {
            dstw += 1;
        }
        if (dsth % 2 !== 0) {
            dsth += 1;
        }

        let x0 = dstw / 2;
        let y0 = dsth / 2;

        const incrementX = Math.floor(srcw / 2 - x0);
        const incrementY = Math.floor(srch / 2 - y0);

        let dstData = new Array(dstw);
        for (let i = 0; i < dstw; i++) {
            dstData[i] = new Array(dsth);
        }
        // 先旋转 后缩放
        let cos = matrix.a;
        let sin = matrix.b;

        this.rotateBilinear(data, dstData, incrementX, incrementY, x0, y0, cos, sin);

        dstData = this.scaleData(dstw, dsth, dstData, 2);


        // // 目标矩阵
        // let dstData = new Array(dstw);
        // for (let i = 0; i < dstw; i++) {
        //     dstData[i] = new Array(dsth);
        //     for (let j = 0; j < dsth; j++) {
        //         // 将目标图像坐标平移到旋转中心上
        //         let src_x = i - rot_center_x;
        //         let src_y = j - rot_center_y;

        //         // 计算目标图像坐标绕旋转中心旋转后的位置
        //         let rot_x = smatrix[0] * src_x + smatrix[2] * src_y;
        //         let rot_y = smatrix[1] * src_x + smatrix[3] * src_y;

        //         // 将旋转后的坐标平移回原来的位置
        //         let src_x_old = rot_x + center_x;
        //         let src_y_old = rot_y + center_y;

        //         // 双线性插值法
        //         let x1 = Math.floor(src_x_old);
        //         let x2 = Math.floor(src_x_old + 0.5);
        //         let y1 = Math.floor(src_y_old);
        //         let y2 = Math.floor(src_y_old + 0.5);

        //         x1 = x1 < 0 ? 0 : x1;
        //         y1 = y1 < 0 ? 0 : y1;


        //         x1 = x1 < srcw - 1 ? x1 : srcw - 1;
        //         y1 = y1 < srch - 1 ? y1 : srch - 1;

        //         x2 = x2 < srcw - 1 ? x2 : srcw - 1;
        //         y2 = y2 < srch - 1 ? y2 : srch - 1;

        //         // 取出原矩阵中对应四个点的值
        //         let f11 = data[x1][y1];
        //         let f21 = data[x1][y2];
        //         let f12 = data[x2][y1];
        //         let f22 = data[x2][y2];
        //         // 计算该点的值
        //         let xm = src_x_old - x1;
        //         let ym = src_y_old - y1;
        //         let r1 = (1 - xm) * f11 + xm * f21;
        //         let r2 = (1 - xm) * f12 + xm * f22;
        //         let value = (1 - ym) * r1 + ym * r2;

        //         value = Math.round(value);
        //         if (value < 0) {
        //             value = 0;
        //         }
        //         if (value > 255) {
        //             value = 255;
        //         }

        //         dstData[i][j] = value;
        //     }
        // }
        return dstData;
    }

    // gamma 矫正
    gammaCorrection = function (data, w, h, gamma) {
        let buildTable = function (fPrecompensaton) {
            let gammaLUT = new Array(256);
            for (let i = 0; i < 256; i++) {
                gammaLUT[i] = Math.round(Math.pow(i / 255, fPrecompensaton) * 255);
                if (gammaLUT[i] < 0) gammaLUT[i] = 0;
                if (gammaLUT[i] > 255) gammaLUT[i] = 255;
            }
            return gammaLUT;
        }

        let gammaLUT = buildTable(gamma);
        for (let i = 0; i < w; i++) {
            for (let j = 0; j < h; j++) {
                data[i][j] = gammaLUT[data[i][j]];
            }
        }
    }

    gammaDecode = function (c) {
        // converts a sRGB input (in the range 0.0-1.0) to linear color space
        if (c <= 0.04045) 
            return c / 12.02;
        else 
            return Math.pow((c + 0.055) / 1.055, 2.4);
    }

    gammaEncode = function (c) {
        // converts a linear input (in the range 0.0-1.0) to sRGB color space
        if (c <= 0.0031308)  // 取值 0 是无法计算，避免无穷
            return c * 12.92; 
        else 
            return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
    }

    // gamma -1 - 1 , 默认0
    adjustSourceImage = function (data, w, h, gamma, brightness, contrast) {
        for (let i = 0; i < w; i++) {
            for (let j = 0; j < h; j++) {
                let c = data[i][j] / 255;
                c = Math.pow(c, 1.0 / (gamma + 1.0));
                c = (c - 0.5) * (contrast + 1.0) + 0.5 + brightness;
                c = c < 0.0 ? 0.0 : (c > 1.0? 1.0 : c);
                let value = Math.round(this.gammaEncode(c) * 255);
                data[i][j] = value;
            }
        }
    }

    deleteWhite = function (data, w, h) {
        for (let i = 0; i < w; i++) {
            for (let j = 0; j < h; j++) {
                if (data[i][j] > 240) {
                    data[i][j] = 255;
                } 
            }
        }
    }

    isBackgroundWhite = function (data, w, h) {
        // 取四个角的色块, 如果有三个角的色块都是白色, 默认为背景白
        let ret = false; // 默认不是背景白
        let whiteBlockCount = 0;
        if (w < 10 || h < 10) return ret; // 矩阵太小, 无法判断
        // 左小角
        let leftTop = 0;
        let leftBottom = 0;
        let rightTop = 0;
        let rightBottom = 0;
        for (let i = 0; i < 5; i++){
            for (let j = 0; j < 5; j++){
                if (data[i][j] > 250) leftTop++;
                if (data[w - i - 1][j] > 250) leftBottom++;
                if (data[i][h - j - 1] > 250) rightTop++;
                if (data[w - i - 1][h - j - 1] > 250) rightBottom++;
            }
        }
        if (leftTop >= 23) whiteBlockCount++;
        if (leftBottom >= 23) whiteBlockCount++;
        if (rightTop >= 23) whiteBlockCount++;
        if (rightBottom >= 23) whiteBlockCount++;
        if (whiteBlockCount >= 3) ret = true;
        return ret;
    }
}


// 把源矩阵拷贝一份
// let resData = new Array(w);
// for (let i = 0; i < w; i++) {
//     resData[i] = new Array(h);
//     for (let j = 0; j < h; j++) {
//         resData[i][j] = data[i][j];
//     }
// }